window.addEventListener('load', e => {
  const canvas = document.getElementById('canvas');
  let gl = getWebGLContext(canvas);
  initShaders(gl, v_shader_source, f_shader_source);
  let position = new Float32Array([
    1.5, 10.0, 1.5, -1.5, 10.0, 1.5, -1.5, 0.0, 1.5, 1.5, 0.0, 1.5, // v0-v1-v2-v3 front
    1.5, 10.0, 1.5, 1.5, 0.0, 1.5, 1.5, 0.0, -1.5, 1.5, 10.0, -1.5, // v0-v3-v4-v5 right
    1.5, 10.0, 1.5, 1.5, 10.0, -1.5, -1.5, 10.0, -1.5, -1.5, 10.0, 1.5, // v0-v5-v6-v1 up
    -1.5, 10.0, 1.5, -1.5, 10.0, -1.5, -1.5, 0.0, -1.5, -1.5, 0.0, 1.5, // v1-v6-v7-v2 left
    -1.5, 0.0, -1.5, 1.5, 0.0, -1.5, 1.5, 0.0, 1.5, -1.5, 0.0, 1.5, // v7-v4-v3-v2 down
    1.5, 0.0, -1.5, -1.5, 0.0, -1.5, -1.5, 10.0, -1.5, 1.5, 10.0, -1.5  // v4-v7-v6-v5 back
  ]);
  let indices = new Uint8Array([
    0, 1, 2, 0, 2, 3, // front
    4, 5, 6, 4, 6, 7, // right
    8, 9, 10, 8, 10, 11, // up
    12, 13, 14, 12, 14, 15, // left
    16, 17, 18, 16, 18, 19, // down
    20, 21, 22, 20, 22, 23 // back
  ]);
  // 法向量 normal
  let normals = new Float32Array([
    0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
    1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
    0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
    -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
    0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, // v7-v4-v3-v2 down
    0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0 // v4-v7-v6-v5 back
  ]);

  // point data
  let positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
  let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Position);

  let a_Normal = gl.getAttribLocation(gl.program, 'a_Normal');
  let normalBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
  gl.vertexAttribPointer(a_Normal, 3, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Normal);

  let a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  gl.vertexAttrib3f(a_Color, 1.0, 0.0, 0.0);//vertexAttrib4fv

  let u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
  let u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');
  let u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
  gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
  gl.uniform3f(u_LightPosition, 2.3, 4.0, 3.5);
  gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);

  let indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.enable(gl.DEPTH_TEST);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  let matrix = new Matrix4();
  matrix.setPerspective(50, canvas.width / canvas.height, 1, 100);
  matrix.lookAt(20.0, 10.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  let u_Matrix = gl.getUniformLocation(gl.program, 'u_Matrix');

  let u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');

  let u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');

  const animate = (bottomRotateYAngle, upRotateZAngle) => {
    let modelMatrix = new Matrix4();
    modelMatrix.setRotate(bottomRotateYAngle, 0, 1, 0).translate(0.0, -10.0, 0.0);

    let mvpMatrix = new Matrix4();
    mvpMatrix.setPerspective(50, canvas.width / canvas.height, 1, 100);
    mvpMatrix.lookAt(20.0, 10.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    mvpMatrix.multiply(modelMatrix);

    let normalMatrix = new Matrix4();
    normalMatrix.setInverseOf(modelMatrix).transpose();

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    draw(mvpMatrix, modelMatrix, normalMatrix, [1.0, 0.0, 0.0]);

    mvpMatrix.multiply(new Matrix4().setInverseOf(modelMatrix));

    modelMatrix.translate(0.0, 10.0, 0.0).scale(1.3, 1.0, 1.3).rotate(upRotateZAngle, 0, 0, 1);
    mvpMatrix.multiply(modelMatrix);

    normalMatrix.setInverseOf(modelMatrix).transpose();
    draw(mvpMatrix, modelMatrix, normalMatrix, [0.0, 1.0, 0.0]);
  };

  const draw = (matrix, modelMatrix, normalMatrix, color) => {

    gl.uniformMatrix4fv(u_Matrix, false, matrix.elements);

    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

    gl.vertexAttrib3fv(a_Color, color);

    gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);
    gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
  }


  let ANGLE_STEP = 3.0;
  let bottomRotateYAngle = 0;
  let upRotateZAngle = 0;
  animate(bottomRotateYAngle, upRotateZAngle);
  document.addEventListener('keydown', e => {
    switch (e.keyCode) {
      case 38: // Up arrow key -> the positive rotation of joint1 around the z-axis
        if (upRotateZAngle < 135.0) upRotateZAngle += ANGLE_STEP;
        break;
      case 40: // Down arrow key -> the negative rotation of joint1 around the z-axis
        if (upRotateZAngle > -135.0) upRotateZAngle -= ANGLE_STEP;
        break;
      case 39: // Right arrow key -> the positive rotation of arm1 around the y-axis
        bottomRotateYAngle = (bottomRotateYAngle + ANGLE_STEP) % 360;
        break;
      case 37: // Left arrow key -> the negative rotation of arm1 around the y-axis
        bottomRotateYAngle = (bottomRotateYAngle - ANGLE_STEP) % 360;
        break;
      default: return; // Skip drawing at no effective action
    }
    animate(bottomRotateYAngle, upRotateZAngle);
  });

});


const v_shader_source = `
  attribute vec4 a_Position;
  attribute vec4 a_Normal;
  attribute vec4 a_Color;

  uniform mat4 u_Matrix;
  uniform mat4 u_ModelMatrix;
  uniform mat4 u_NormalMatrix;

  varying vec3 v_Position;
  varying vec3 v_Normal;
  varying vec4 v_Color;

  void main(){
    gl_Position = u_Matrix * a_Position;

    // world position
    v_Position = (u_ModelMatrix * a_Position).xyz;
    
    // normal
    v_Normal = normalize(u_NormalMatrix * a_Normal).xyz;
    v_Color = a_Color;
  }
`;

const f_shader_source = `
  precision lowp float;

  uniform vec3 u_LightColor;
  uniform vec3 u_LightPosition;
  uniform vec3 u_AmbientLight;

  varying vec3 v_Position;
  varying vec3 v_Normal;
  varying vec4 v_Color;

  void main(){
    vec3 lightDirection = normalize(u_LightPosition - v_Position);
    
    // point light
    float nDotL = max(dot(lightDirection, normalize(v_Normal)), 0.0);
    vec3 diffuse = u_LightColor * v_Color.rgb * nDotL;
    
    // ambient light
    vec3 ambient = u_AmbientLight * v_Color.rgb;

    gl_FragColor = vec4(diffuse + ambient, v_Color.a);
  }
`;